package com.sql;

import java.sql.*;
import java.util.*;
import java.io.*;
import java.util.Date;

public class ConnectionPool
{

    public DbConnectionPool connectionPool = null;
	public boolean newpool=false;
	private String driver,server,username,password;
	private int minConnections,maxConnections,connectionTimeout,maxFreetimeout;
	private boolean debug;

    private Object initLock = new Object();

	/**
	 * 连接池构造函数
	 */
    public ConnectionPool(){
	    start();
    }

	public void init(){
		try{
			PropertyManager.reset();
			this.driver=PropertyManager.getProperty("driver");
			this.server=PropertyManager.getProperty("url");
			this.username=PropertyManager.getProperty("username");
			this.password=PropertyManager.getProperty("password");
			this.minConnections=Integer.parseInt(PropertyManager.getProperty("minconnections"));
			this.maxConnections=Integer.parseInt(PropertyManager.getProperty("maxconnections"));
			this.connectionTimeout=Integer.parseInt(PropertyManager.getProperty("connectiontimeout"));
			this.maxFreetimeout=Integer.parseInt(PropertyManager.getProperty("maxfreetimeout"));
			this.debug=Boolean.valueOf(PropertyManager.getProperty("debug")).booleanValue();
		}catch(Exception e){
			e.printStackTrace();
		}
	}

    /**
	 * 获取连接函数
	 *@return Connection
	 */
    public Connection getConnection() {
        if (connectionPool == null) {
            synchronized(initLock) {
                if (connectionPool == null) {
                    return null;
                }
            }
        }
        return new ConnectionWrapper(connectionPool.getConnection(), connectionPool);
    }

    public int getUseCount() {
        if (connectionPool == null) {
            synchronized(initLock) {
                if (connectionPool == null) {
                    return -1;
                }
            }
        }
        return connectionPool.getUseCount();
    }

    public int getSize() {
        if (connectionPool == null) {
            synchronized(initLock) {
                if (connectionPool == null) {
                    return -1;
                }
            }
        }
        return connectionPool.getSize();
    }

    /**
	 * 连接池启动函数
	 */
    public void start() {
        synchronized (initLock) {
            try {
				if(connectionPool==null){
					init();
					connectionPool = new DbConnectionPool();
				}
            }
            catch (IOException ioe) {
                if(debug)
				   System.err.println("Error starting DbConnectionDefaultPool: " + ioe);
            }
        }
    }

    /**
	 * 连接池重启函数
	 *@return Connection
	 */
    public void restart() {
        //Kill off pool.
        destroy();
		//Start a new pool.
        start();
    }
 
    /**
	 * 破坏连接池函数
	 *@return Connection
	 */
    public void destroy() {
        if (connectionPool != null) {
            try {
                connectionPool.destroy();
            }
            catch (Exception e) {
                if(debug){
					System.out.println("destroy connection pool error......");
				}
            }
        }
    }

    private class DbConnectionPool implements Runnable {
        private Thread runner;

        private Connection[] connPool;
        private int[] connStatus;

        private long[] connLockTime, connCreateDate;
        private String[] connID;

        private boolean available=true;
		
		private long poolLockTime;

		private int currConnections,maxConnMSec,connLast;

        public DbConnectionPool () throws IOException
        {
			connPool = new Connection[maxConnections];
            connStatus = new int[maxConnections];
            connLockTime = new long[maxConnections];
            connCreateDate = new long[maxConnections];
            connID = new String[maxConnections];

			poolLockTime=System.currentTimeMillis();

			currConnections=minConnections;

            maxConnMSec=connectionTimeout;

            boolean connectionsSucceeded=false;
            int dbLoop=5;
            try {
                for(int i=1; i < dbLoop; i++) {
                    try {
                        for(int j=0; j < currConnections; j++) {
                            createConn(j);
                        }
                        connectionsSucceeded=true;
                        break;
                    }
                    catch (SQLException e){
                        if(debug)
						{
						 System.out.println("--->Attempt (" + String.valueOf(i) +
                                " of " + String.valueOf(dbLoop) +
                                ") failed to create new connections set at startup: ");
                         System.out.println("    " + e);
                         System.out.println("    Will try again in 10 seconds...");
						}
                        try { Thread.sleep(10000); }
                        catch(InterruptedException e1) {}
                    }
                }
                if(!connectionsSucceeded) { // All attempts at connecting to db exhausted
                    if(debug)
					  System.out.println("\r\nAll attempts at connecting to Database exhausted");
                    throw new IOException();
                }else{
					if(debug)
						System.out.println("connection pool started...");
				}
            }
            catch (Exception e) {
                e.printStackTrace();
                throw new IOException();
            }

            runner = new Thread(this);
            runner.start();

        } //End ConnectionPool()

        public void run() {
            boolean forever = true;
            Statement stmt=null;

            while(forever) {
				if(System.currentTimeMillis() - poolLockTime>maxFreetimeout){
					destroy();
					connectionPool=null;
					if(debug)
						System.out.println("Max free timeout !!! Destroy pool....");
                    return;
				}


                for(int i=0; i < currConnections; i++) { // Do for each connection
                    long age = System.currentTimeMillis() - connCreateDate[i];
					long runage = System.currentTimeMillis() - connLockTime[i];

					try {  // Test the connection with createStatement call
                        synchronized(connStatus) {

						if(connStatus[i] > 0 && runage>maxConnMSec){
							if(debug)
							   System.out.println("connection " + i + " timeout!!");
							freeConnection(connPool[i]);
						}
						
						if(connStatus[i] > 0 && !connPool[i].isClosed()) { // In use, catch it next time!
                            continue;
                        }

                        if(age > 2*maxConnMSec && connStatus[i]==0) {  // Force a reset at the max conn time
							if(debug)
							   System.out.println("connection " + i + " maxtimeout!!");
							throw new SQLException();
                        }

						if(connPool[i].isClosed()) {
                            if(debug)
							   System.out.println("connection " + i + " isclosed!!");
							throw new SQLException();
                        }                       

                        stmt = connPool[i].createStatement();
                        connStatus[i] = 0;  // Connection is O.K.
						}

                    }
                    catch(SQLException e) {
                        try {
                            connPool[i].close();
						    if(currConnections>minConnections && i==currConnections-1 && connPool[i].isClosed())
							{
								currConnections--;
								if(debug)
									System.out.println("cancel connection " + String.valueOf(i));
							}
							else
                               createConn(i);
                        }
                        catch(SQLException e1) {
                            if(debug)
							  System.out.println("Failed: " + e1);
                            connStatus[i] = 0;  // Can't open, try again next time
                        }
                    }
                    finally {
                        try {
                            if(stmt != null) {
                                stmt.close();
                            }
                        }
                        catch(SQLException e1){};
                    }
                }

                try {
                    Thread.sleep(1000);
                }  // Wait 1      seconds for next cycle
                catch(InterruptedException e) {
                    return;
                }
            }
        } // End run

        public Connection getConnection() {

            poolLockTime=System.currentTimeMillis();

            Connection conn=null;

            if(available){
                boolean gotOne = false;

                for(int outerloop=1; outerloop<=5; outerloop++) {

                    try  {
                        int loop=0;
                        int roundRobin = connLast + 1;
                        if(roundRobin >= currConnections) roundRobin=0;

                        do {
                            synchronized(connStatus) {
								if((connStatus[roundRobin] < 1) && (! connPool[roundRobin].isClosed()))
                                {
                                    conn = connPool[roundRobin];
                                    connStatus[roundRobin]=1;
                                    connLockTime[roundRobin] = System.currentTimeMillis();
                                    connLast = roundRobin;
                                    gotOne = true;
                                    break;
                                }
                                else {
                                    loop++;
                                    roundRobin++;
                                    if(roundRobin >= currConnections) roundRobin=0;
                                }
                            }
                        }
                        while((gotOne==false)&&(loop < currConnections));
                    }
                    catch (SQLException e1) {}

                    if(gotOne) {
                        break;
                    }
                    else {
                        synchronized(this) {  // Add new connections to the pool
                            if(currConnections < maxConnections) {
                                try {
                                     createConn(currConnections);
                                     currConnections++;
                                }
                                catch(SQLException e) {
                                    if(debug)
									  System.out.println("Unable to create new connection: " + e);
                                }
                            }
                        }

                        try { Thread.sleep(2000); }
                        catch(InterruptedException e) {}
                        if(debug)
						  System.out.println("-----> Connections Exhausted!  Will wait and try " +
                            "again in loop " +
                            String.valueOf(outerloop));
                    }
                } // End of try 10 times loop

            }
            else {
                if(debug)
				  System.out.println("Unsuccessful getConnection() request during destroy()");
            } // End if(available)

			if(debug)
			  System.out.println("get connection " + idOfConnection(conn));
            return conn;
        }

        public int idOfConnection(Connection conn) {
            int match;
            String tag;

            try {
                tag = conn.toString();
            }
            catch (NullPointerException e1) {
                tag = "none";
            }

            match=-1;

            for(int i=0; i< currConnections; i++) {
                if(connID[i].equals(tag)) {
                    match = i;
                    break;
                }
            }
            return match;
        }

        public void freeConnection(Connection conn) {
            int thisconn = idOfConnection(conn);
            if(thisconn >= 0) {
                connStatus[thisconn]=0;
				if(debug)
					System.out.println("release connection " + idOfConnection(conn));
            }
            else {
                if(debug)
				   System.out.println("----> Could not free connection!!!");
            }
        }

        private void createConn(int i) throws SQLException {
             Date now = new Date();
             try {
                Class.forName (driver);
                connPool[i] = DriverManager.getConnection(server,username,password);
                connStatus[i]=0;
                connID[i]=connPool[i].toString();
                connLockTime[i]=0;
                connCreateDate[i] =  now.getTime();

                if(debug)
				  System.out.println("Creating connection " + String.valueOf(i));
            }
            catch (ClassNotFoundException e2) {
                throw new SQLException(e2.getMessage());
            }
        }

        public void destroy(int millis) throws SQLException {
            available=false;

            runner.interrupt();

            try { runner.join(millis); }
            catch(InterruptedException e){} 

            for(int i=0; i < currConnections; i++) {
                try {
                    connPool[i].close();
                }
                catch (SQLException e1)
                {
                    if(debug)
					  System.out.println("Cannot close connections on Destroy");
                }
            }

            //if(useCount > 0) {
                //bt-test successful
              //  String msg="Unsafe shutdown: Had to close "+useCount+
                           // " active DB connections after "+millis+"ms";
              //  if(debug)
			//	  System.out.println(msg);
              //  throw new SQLException(msg);
            //}

			if(debug)
				System.out.println("connection pool destroyed...");


        }//End destroy()


        public void destroy() {
            try {
                destroy(20000);
				connectionPool=null;
            }
            catch(SQLException e) {}
        }

        public int getUseCount() {
            int useCount=0;
            synchronized(connStatus) {
                for(int i=0; i < currConnections; i++) {
                    if(connStatus[i] > 0) { // In use
                        useCount++;
                    }
                }
            }
            return useCount;
        }//End getUseCount()

        public int getSize() {
            return currConnections;
        }//End getSize()

    } // End class

    public class ConnectionWrapper implements Connection {

        private Connection connection;
        private DbConnectionPool connectionPool;
		private Statement stmt=null;

        public ConnectionWrapper(Connection connection, DbConnectionPool connectionPool) {
            this.connection = connection;
            this.connectionPool = connectionPool;
        }

        public void close() {
			try{
			if(this.stmt!=null){
				stmt.close();
		        stmt=null;
				if(debug)
				  System.out.println("statement closed...");
			}
			}catch(Exception e){
                System.out.println("statement close error....");
			}
            connectionPool.freeConnection(this.connection);
            connection = null;
            connectionPool = null;
        }

        public void setHoldability(int holdability) throws SQLException {
		    connection.setHoldability(holdability);
		}

        public int getHoldability() throws SQLException {
			return connection.getHoldability();
		}

        public Savepoint setSavepoint() throws SQLException {
            return connection.setSavepoint();
		}

        public Savepoint setSavepoint(String name) throws SQLException {
			return connection.setSavepoint(name);
		}

		public void rollback(Savepoint savepoint) throws SQLException {
			connection.rollback(savepoint);
		}

		public void releaseSavepoint(Savepoint savepoint) throws SQLException {
			connection.releaseSavepoint(savepoint);
		}

        public Statement createStatement(int resultSetType,int resultSetConcurrency,int resultSetHoldability) throws SQLException {
		    return connection.createStatement(resultSetType,resultSetConcurrency,resultSetHoldability);
		}

		public PreparedStatement prepareStatement(String sql,int resultSetType,int resultSetConcurrency,int resultSetHoldability) throws SQLException {
		    return connection.prepareStatement(sql,resultSetType,resultSetConcurrency,resultSetHoldability);
		}

		public CallableStatement prepareCall(String sql,int resultSetType,int resultSetConcurrency,int resultSetHoldability) throws SQLException {
		    return connection.prepareCall(sql,resultSetType,resultSetConcurrency,resultSetHoldability);
		}

		public PreparedStatement prepareStatement(String sql,int autoGeneratedKeys) throws SQLException {
		    return connection.prepareStatement(sql,autoGeneratedKeys);
		}

		public PreparedStatement prepareStatement(String sql,int[] columnIndexes) throws SQLException {
		    return connection.prepareStatement(sql,columnIndexes);
		}

		public PreparedStatement prepareStatement(String sql,String[] columnNames) throws SQLException {
		    return connection.prepareStatement(sql,columnNames);
		}

        public Statement createStatement() throws SQLException {
            stmt=connection.createStatement();
			return stmt;
        }

        public PreparedStatement prepareStatement(String sql) throws SQLException {
            return connection.prepareStatement(sql);
        }

        public CallableStatement prepareCall(String sql) throws SQLException {
            return connection.prepareCall(sql);
        }

        public String nativeSQL(String sql) throws SQLException {
            return connection.nativeSQL(sql);
        }

        public void setAutoCommit(boolean autoCommit) throws SQLException {
            connection.setAutoCommit(autoCommit);
        }

        public boolean getAutoCommit() throws SQLException {
            return connection.getAutoCommit();
        }

        public void commit() throws SQLException {
            connection.commit();
        }

        public void rollback() throws SQLException {
            connection.rollback();
        }

        public boolean isClosed() throws SQLException {
            return connection.isClosed();
        }

        public DatabaseMetaData getMetaData() throws SQLException {
            return connection.getMetaData();
        }

        public void setReadOnly(boolean readOnly) throws SQLException {
            connection.setReadOnly(readOnly);
        }

        public boolean isReadOnly() throws SQLException {
            return connection.isReadOnly();
        }

        public void setCatalog(String catalog) throws SQLException {
            connection.setCatalog(catalog);
        }

        public String getCatalog() throws SQLException {
            return connection.getCatalog();
        }

        public void setTransactionIsolation(int level) throws SQLException {
            connection.setTransactionIsolation(level);
        }

        public int getTransactionIsolation() throws SQLException {
            return connection.getTransactionIsolation();
        }

        public SQLWarning getWarnings() throws SQLException {
            return connection.getWarnings();
        }

        public void clearWarnings() throws SQLException {
            connection.clearWarnings();
        }

        public Statement createStatement(int resultSetType, int resultSetConcurrency)
                throws SQLException
        {
            return connection.createStatement(resultSetType, resultSetConcurrency);
        }

        public PreparedStatement prepareStatement(String sql, int resultSetType,
                int resultSetConcurrency) throws SQLException
        {
            return connection.prepareStatement(sql, resultSetType, resultSetConcurrency);
        }

        public CallableStatement prepareCall(String sql, int resultSetType,
                int resultSetConcurrency) throws SQLException
        {
            return connection.prepareCall(sql, resultSetType, resultSetConcurrency);
        }

        @SuppressWarnings("unchecked")
		public Map getTypeMap() throws SQLException {
            return connection.getTypeMap();
        }

    

		public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
			// TODO Auto-generated method stub
			return null;
		}

		public Blob createBlob() throws SQLException {
			// TODO Auto-generated method stub
			return null;
		}

		public Clob createClob() throws SQLException {
			// TODO Auto-generated method stub
			return null;
		}

		public NClob createNClob() throws SQLException {
			// TODO Auto-generated method stub
			return null;
		}

		public SQLXML createSQLXML() throws SQLException {
			// TODO Auto-generated method stub
			return null;
		}

		public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
			// TODO Auto-generated method stub
			return null;
		}

		public Properties getClientInfo() throws SQLException {
			// TODO Auto-generated method stub
			return null;
		}

		public String getClientInfo(String name) throws SQLException {
			// TODO Auto-generated method stub
			return null;
		}

		public boolean isValid(int timeout) throws SQLException {
			// TODO Auto-generated method stub
			return false;
		}

		public void setClientInfo(Properties properties) throws SQLClientInfoException {
			// TODO Auto-generated method stub
			
		}

		public void setClientInfo(String name, String value) throws SQLClientInfoException {
			// TODO Auto-generated method stub
			
		}

		public void setTypeMap(Map<String, Class<?>> arg0) throws SQLException {
			// TODO Auto-generated method stub
			
		}

		public boolean isWrapperFor(Class<?> iface) throws SQLException {
			// TODO Auto-generated method stub
			return false;
		}

		public <T> T unwrap(Class<T> iface) throws SQLException {
			// TODO Auto-generated method stub
			return null;
		}

    }
}
